<?php
/* 
 * Class to handle paginating/extending extendable lists.
 */

class Paginator {

    private $reader;                /* function (taking page_id as its first
                                     * arg) for reading page of element data */
    private $renderer;              /* function (taking single element data obj
                                     * as its first arg) that prints HTML view
                                     * of the element */
    private $service_url;           /* relative URL of service for requesting
                                     * the extension/paging of a list */
    private $page_size;             // num of elements per page
    private $empty_msg;             // msg to show if no elements in list
    private $page_id_pattern;       // regex pattern for validating page ID
    private $list_id;               /* allow client to specify list ID so that
                                     * they can manipulate contents */

    function __construct($reader, $renderer, $service_url, $page_size,
                           $empty_msg, $page_id_pattern, $list_id=null) {
        $this->reader = $reader;
        $this->renderer = $renderer;
        $this->service_url = $service_url;
        $this->page_size = $page_size;
        $this->empty_msg = $empty_msg;
        $this->page_id_pattern = $page_id_pattern;
        $this->list_id = empty($list_id) ? uniqid() : $list_id;
    }

    /**
     * Print HTML for page of elements belonging to extendable list.
     * @param bool $is_update Whether this page is an update to an existing
     * list or the first page of a list (in which case the extendable framework
     * is included in the output).
     * @param string $page_id Page ID to be used when reading first page of
     * list, this value is overwritten when $is_update is true by its namesake
     * in the GET request.
     */
    function print_page($is_update, $page_id=null) {

        // if responding to more request read and validate ID of the DIV element
        // receiving the updated HTML and the ID of the next page being requested
        if ($is_update) {
            $list_id = $_GET['list_id'];
            $page_id = $_GET['page_id'];
            if (!preg_match('/^[0-9a-z]{1,13}$/', $list_id) ||
                !preg_match($this->page_id_pattern, $page_id)) {
                http_bad_request();
            }
        }

        // read next/first page of elements
        $elements = call_user_func($this->reader, $page_id);

        // if first page is being displayed but there are no elements display
        // appropriate 'empty' message
        if (!$is_update && empty($elements) && !empty($this->empty_msg)) {
            print "<div class=\"no-investment-shorts\">$this->empty_msg</div>";
            return;
        }

        /* more elements are read than will be displayed in order to test for the
         * existence for 'more' elements. if more elements do exist remove the
         * last 'test' element from the array. */
        if (count($elements) > $this->page_size) {
            $next_element = array_pop($elements);
        }

        /* print page elements HTML to variable, which will be handled differently
         * depending on whether the first page is being rendered or a subsequent
         * page is being rendered in response to a more request (update) */
        $renderer = $this->renderer;
        if (empty($elements)) {
            $html = '';
        } else {
            $html = capture_print(
                function() use($elements, $renderer) {
                    foreach ($elements as $element) {
                        call_user_func($renderer, $element);
                    }
                });
        }
        if ($is_update) {
            $response['xlist_container_' . $list_id . '*'] = $html;
        } else {
            // if there are more elements place the first page inside an expandable,
            // addressable DIV element
            if (isset($next_element)) {
                $list_id = $this->list_id;
                $html = "<div id=\"xlist_container_$list_id\">$html</div>";
            }
            print $html;
        }

        // print more button
        if (isset($next_element)) {

            // each element has a 'page_id' property which contains data indicating
            // where the next page of elements begins
            $page_id = $next_element['page_id'];

            /* the container ID is passed to the server with the more request so
             * that the server knows the ID of the target DIV that should receive
             * the response HTML */
            $html = '<div class="more">';
            $html .= '<input type="button" class="btn" value="more" ';
            $html .= "onclick=\"javascript:ajax_get('/$this->service_url?";
            $html .= http_build_query(array(
                'list_id' => $list_id,
                'page_id' => $page_id));
            $html .= '\');" /></div>';

            // if a more request is being responded to update the client's more
            // button to request the next page of elements
            if ($is_update) {
                $response['xlist_button_' . $list_id] = $html;
            /* if this is the first page of elements place the more button inside
             * an addressable DIV element so that it can be modified by the server
             * (see above) */
            } else {
                print "<div id=\"xlist_button_$list_id\">$html</div>";
            }

        // if there are no more elements and a more request is being responded to
        // hide the more button
        } else if ($is_update) {
            $response['xlist_button_' . $list_id] = '';
        }

        // if responding to more request (update), format response so that it will
        // be interpreted by the client as an HTML update
        if ($is_update) {
            ajax_response($response);
        }
    }
}

/**
 * Return instance of Paginator class for list of complete investments.
 * @return Paginator
 */
function get_paginator_for_complete_investments() {
    global $_CONFIG;
    return new Paginator(
        'read_investments',
        'print_html_investment_short',
        'more-investments',
        $_CONFIG->investments_per_page,
        null,
        '/^[0-9]{9,11}$/'); /* 1 Jan 1990 = 631152000 (9 chars)
                             * 1 Jan 3000 = 32503680000 (11 chars) */
}

/**
 * Return instance of Paginator class for list of candidate investments.
 * @return Paginator
 */
function get_paginator_for_candidate_investments() {
    global $_CONFIG;
    return new Paginator(
        'read_upcoming_investments',
        'print_html_investment_short',
        'more-upcoming',
        $_CONFIG->candidates_per_page,
        NO_INVESTMENT_SHORTS_CANDIDATE,
        '/^[0-9]{1,10}$/');
}

/**
 * Return instance of Paginator class for list of comments belonging to
 * investment.
 * @return Paginator
 */
function get_paginator_for_comments() {
    global $_CONFIG;
    return new Paginator(
        'read_comments',
        'print_html_comment',
        'more-comments',
        $_CONFIG->comments_per_page,
        NO_COMMENTS,
        '/^([0-9a-z]{13}),[0-9a-z]{13}$/',
        'comments');
}

?>
